package com.spring.beans;

import com.spring.config.BeanNameAware;
import com.spring.config.BeanPostProcessor;
import com.spring.config.InitializingBean;
import com.spring.context.*;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HscApplicationContext {
    //成员属性，闯入Class类型参数
    private Class configClass;
    //创建一个Map集合存放对象的定义信息，其实就是类的描述信息，用于后续创建对象
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    //存放完整的对象，创建完成的，类似spring的一级缓存
    private Map<String, Object> singletonObjectsMap = new HashMap<>();

    //存饭增强器对象的集合
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    /**
     * 在创建对象时就直接将bean创建出来
     *
     * @param configClass
     */
    public HscApplicationContext(Class configClass) {
        //给当前的成员属性configClass赋值
        this.configClass = configClass;
        //1、传入需要spring管理的配置来创建对象
        //2、扫描配置类上注解的要扫描的包，看那些类要创建对象
        scan(configClass);
        //3、扫描完所有要创建对象的类，开始遍历创建对象，根据BeanDefinition创建对象
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            //获取对象的名称
            String beanName = entry.getKey();
            //获取对应的BeanDefinition
            BeanDefinition beanDefinition = entry.getValue();
            //判断当前的beanDefinition是否时单例
            if (beanDefinition.getScope().equals("singleton")) {
                //单例对象直接创建对象
                Object bean = createBean(beanName, beanDefinition);
                //将对象放入缓存中，也就是spring常说的一级缓存，获取对象的时候可以直接进行获取到
                singletonObjectsMap.put(beanName, bean);
            }

        }

    }

    /**
     * 创建对象方法
     *
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        //获取当前的class对象
        Class definitionType = beanDefinition.getType();
        Object instance = null;
        try {
            // 【实例化对象】：根据Class获取对应的构造器，实例化，创建对象
            instance = definitionType.getConstructor().newInstance();

            //完成属性的赋值操作：获取所有的属性值
            for (Field field : definitionType.getDeclaredFields()) {
                //如果有自动注入的话，给属性赋值
                if (field.isAnnotationPresent(Autowired.class)) {
                    //强力破解私有属性
                    field.setAccessible(true);
                    //给当前的对象instance属性进行赋值 注意这里当一个对象里面依赖着另外的对象，可能这个属性并没进行创建，这里就很像spring说的循环依赖的问题
                    field.set(instance, getBean(field.getName()));
                }
            }

            //在初始化之前对象对实现了Aware接口的对象做名称的赋值
            if (instance instanceof BeanNameAware) {
                //如果该对象实现了BeanNameAware接口
                ((BeanNameAware) instance).setBeanName(beanName);
            }
            //看当前的对象是否要做前置增强，如果需要做一些增强处理
            //获取所有的增强器对象
            //前置增强，在初始化之前，我们可以对bean做一些操作，在这里对自定义的注解实现名字的实现
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            }
            //【初始化bean】调用实现了InitializingBean接口的方法
            if (instance instanceof InitializingBean) {
                //如果当前的对象实现了InitializingBean接口，调用方法
                ((InitializingBean) instance).afterPropertiesSet();
            }
            //初始化后的后置增强,这里做aop的功能，生成代理对象
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //接受代理对象的返回，这里一定要记得接收返回值（代理的对象）
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return instance;
    }

    /**
     * 扫描包下所有的注解
     *
     * @param configClass
     */
    private void scan(Class configClass) {
        //1、根据ComponetScan类型获取当前的配置的类上的注解信息
        ComponentScan configClassAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        //2、获取注解上value值 为一组数组：com.spring.service com.spring.context
        String[] paths = configClassAnnotation.value();
        //创建一个集合来装替换后得路径
        List<String> listPaths = new ArrayList<>();
        //3、将值中的.替换为 /   com.spring.service转为com/spring/service 变成文件夹路径
        for (String path : paths) {
            path=path.replace(".", "/");
            listPaths.add(path);
        }
        //获取类加载器
        ClassLoader classLoader = HscApplicationContext.class.getClassLoader();
        //扫描这两个包下面的类
        listPaths.stream().forEach(path -> {
            //根据类加载器获取对应的URL信息
            URL resource = classLoader.getResource(path);
            //5、读取文件夹路径com/spring/service 转为File对象
            File file = new File(resource.getFile());
            //检查是否是否是一个目录（文件夹）
            if (file.isDirectory()) {
                //获取指定包下target下所有的class文件
                for (File f : file.listFiles()) {
                    //获取的当前文件的绝对路径： D:\java\projectCode\hsc-spring\target\classes\com\spring\service\OrderService.class
                    String absolutePath = f.getAbsolutePath();
                    System.out.println(absolutePath + "==============================");
                    //路径截取 D:\java\projectCode\hsc-spring\target\classes\com\spring\service\UserService.class 截取为：com\spring\service\UserService
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    // com\spring\service\UserService转为--->com.spring.service.UserService ,
                    // \符号替代为 . 转义字符："\\" 表示一个字面上的反斜杠字符 \，则需要写成 \\
                    absolutePath = absolutePath.replace("\\", ".");

                    try {
                        //根据类文件的相对路径加载class文件
                        Class<?> clazz = classLoader.loadClass(absolutePath);

                        //过滤所有的类，看那个类上面放有@Component注解
                        //判断当前类是否是包存在@Component注解
                        if (clazz.isAnnotationPresent(Component.class)) {
                            //【处理增强器】判断当前的类型有没有实现BeanPostProcessor这个增强的接口
                            if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                                //注意这里用的是clazz对象进行判断， 并不是用对象来进行判断是否实现BeanPostProcessor接口
                                //如果实现了该接口，说明当前对象是一个增强器
                                //将当前的对象强转为BeanPostProcessor
                                BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
                                //将当前的增强对象添加到集合中
                                beanPostProcessorList.add(instance);
                            }
                            //获取当前类上的注解对象
                            Component componentAnnotation = clazz.getAnnotation(Component.class);
                            //获取创建对象的名称
                            String beanName = componentAnnotation.value();
                            //获取该注解中的value值，如果注解value不设置默认为""
                            if ("".equals(beanName)) {
                                //如果value值为"",说明没有设置对象的名称
                                // 如果为"" ,根据类名生成对象名称（beanName）
                                beanName = Introspector.decapitalize(clazz.getSimpleName());
                            }

                            //创建bean的定义信息对象
                            BeanDefinition beanDefinition = new BeanDefinition();
                            //设置对象的类型属性
                            beanDefinition.setType(clazz);

                            //判断当前的类对象，是否设置有@Scope注解
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                //如果当前的类标注有@Scope注解
                                //判断设置的值是单例还是原型
                                //获取注解对象信息
                                Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                String scopeValue = scopeAnnotation.value();
                                //设置当前对象的定义信息
                                beanDefinition.setScope(scopeValue);
                            } else {
                                //如果当前的类上面没有标注有注解，默认是单例的对象，直接进行设置
                                beanDefinition.setScope("singleton");
                            }
                            //完成对当前的类定义信息，放入集合中，为后续的创建对象做准备
                            beanDefinitionMap.put(beanName, beanDefinition);
                        }
                    } catch (ClassNotFoundException | NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    } catch (InstantiationException e) {
                        throw new RuntimeException(e);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }

                }
            }
        });
    }

    /**
     * 根据beanName的名称获取
     *
     * @param beanName
     */
    public Object getBean(String beanName) {
        //判断beanDefinition是否存在要创建的bean
        if (!this.beanDefinitionMap.containsKey(beanName)) {
            //如果不存在直接抛出异常，说明该对象并不是由我们进行管理的
            throw new NullPointerException();
        }
        //判断传入要获取的bean是否是单例的bean,获取BeanDefinition中的定义信息
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //判断是否是单例的
        if (beanDefinition.getScope().equals("singleton")) {
            //直接从集合中取 ，因为单例的bean启动的时候已经创建好了
            Object singletonObject = this.singletonObjectsMap.get(beanName);
            //这里注意一个问题，当一个对象创建时依赖着一个属性对象时,这个属性并没有创建，一个执行顺序的问题，这里singletonObject获取到的对象可能是为空的
            if (singletonObject == null) {
                //如果为空创建对象
                singletonObject = createBean(beanName, beanDefinition);
                //单例对象放到缓存中
                singletonObjectsMap.put(beanName, singletonObject);
            }
            return singletonObject;
        } else {
            //原型的bean，重新进行对象的创建
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }


    }
}
